﻿namespace NServiceBus.AcceptanceTests.PublishSubscribe;

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AcceptanceTesting;
using Configuration.AdvancedExtensibility;
using EndpointTemplates;
using Extensibility;
using Features;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using Persistence;
using Unicast.Subscriptions;
using Unicast.Subscriptions.MessageDrivenSubscriptions;

public class When_configuring_subscription_authorizer : NServiceBusAcceptanceTest
{
    [Test]
    public async Task Should_apply_authorizer_on_subscriptions()
    {
        var context = await Scenario.Define<Context>()
            .WithEndpoint<Subscriber>(e => e
                .When(async s =>
                {
                    await s.Subscribe<ForbiddenEvent>();
                    await s.Subscribe<AllowedEvent>();
                }))
            .WithEndpoint<PublisherWithAuthorizer>()
            .Done(c => c.ReceivedAllowedEventSubscriptionMessage && c.ReceivedForbiddenEventSubscriptionMessage)
            .Run();

        Assert.That(context.SubscriptionStorage.SubscribedEvents, Has.Count.EqualTo(1));
        Assert.That(context.SubscriptionStorage.SubscribedEvents.Single(), Is.EqualTo(typeof(AllowedEvent).FullName));
    }

    class Context : ScenarioContext
    {
        public bool ReceivedAllowedEventSubscriptionMessage { get; set; }
        public bool ReceivedForbiddenEventSubscriptionMessage { get; set; }
        public FakePersistence.FakeSubscriptionStorage SubscriptionStorage { get; set; }
    }

    class Subscriber : EndpointConfigurationBuilder
    {
        public Subscriber()
        {
            var defaultServer = new DefaultServer
            {
                TransportConfiguration = new ConfigureEndpointAcceptanceTestingTransport(false, false)
            };
            EndpointSetup(defaultServer, (c, __) =>
            {
                var routingSettings = new RoutingSettings<AcceptanceTestingTransport>(c.GetSettings());
                routingSettings.DisablePublishing();
            }, p =>
            {
                p.RegisterPublisherFor<ForbiddenEvent, PublisherWithAuthorizer>();
                p.RegisterPublisherFor<AllowedEvent, PublisherWithAuthorizer>();
            });
        }
    }

    class PublisherWithAuthorizer : EndpointConfigurationBuilder
    {
        public PublisherWithAuthorizer()
        {
            var defaultServer = new DefaultServer
            {
                TransportConfiguration = new ConfigureEndpointAcceptanceTestingTransport(false, false),
            };
            EndpointSetup(defaultServer, (endpointConfiguration, descriptor) =>
            {
                endpointConfiguration.UsePersistence<FakePersistence>();
                endpointConfiguration.RegisterStartupTask<StorageAccessor>();
                endpointConfiguration.OnEndpointSubscribed<Context>((args, ctx) =>
                {
                    if (args.MessageType.Contains(typeof(AllowedEvent).FullName))
                    {
                        ctx.ReceivedAllowedEventSubscriptionMessage = true;
                    }
                    if (args.MessageType.Contains(typeof(ForbiddenEvent).FullName))
                    {
                        ctx.ReceivedForbiddenEventSubscriptionMessage = true;
                    }
                });

                var routingSettings =
                    new RoutingSettings<AcceptanceTestingTransport>(endpointConfiguration.GetSettings());
                routingSettings.SubscriptionAuthorizer(ctx =>
                {
                    // only allow subscriptions for AllowedEvent:
                    return ctx.MessageHeaders[Headers.SubscriptionMessageType]
                        .Contains(typeof(AllowedEvent).FullName);
                });
            });
        }
        class StorageAccessor : FeatureStartupTask
        {
            public StorageAccessor(ISubscriptionStorage subscriptionStorage, Context testContext) => testContext.SubscriptionStorage = (FakePersistence.FakeSubscriptionStorage)subscriptionStorage;

            protected override Task OnStart(IMessageSession session, CancellationToken cancellationToken = default) => Task.CompletedTask;

            protected override Task OnStop(IMessageSession session, CancellationToken cancellationToken = default) => Task.CompletedTask;
        }
    }

    public class AllowedEvent : IEvent;

    public class ForbiddenEvent : IEvent;

    class FakePersistence : PersistenceDefinition, IPersistenceDefinitionFactory<FakePersistence>
    {
        FakePersistence() => Supports<StorageType.Subscriptions, SubscriptionStorageFeature>();

        class SubscriptionStorageFeature : Feature
        {
            protected override void Setup(FeatureConfigurationContext context) => context.Services.AddSingleton<ISubscriptionStorage>(new FakeSubscriptionStorage());
        }

        public class FakeSubscriptionStorage : ISubscriptionStorage
        {
            public ConcurrentBag<string> SubscribedEvents { get; } = [];

            public Task Subscribe(Unicast.Subscriptions.MessageDrivenSubscriptions.Subscriber subscriber, MessageType messageType, ContextBag context, CancellationToken cancellationToken = default)
            {
                SubscribedEvents.Add(messageType.TypeName);
                return Task.CompletedTask;
            }

            public Task Unsubscribe(Unicast.Subscriptions.MessageDrivenSubscriptions.Subscriber subscriber, MessageType messageType, ContextBag context, CancellationToken cancellationToken = default) => throw new NotImplementedException();

            public Task<IEnumerable<Unicast.Subscriptions.MessageDrivenSubscriptions.Subscriber>> GetSubscriberAddressesForMessage(IEnumerable<MessageType> messageTypes, ContextBag context, CancellationToken cancellationToken = default) => throw new NotImplementedException();
        }

        public static FakePersistence Create() => new();
    }
}